/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package session

import (
	"bytes"
	"encoding/gob"
	"gitee.com/tugoer/log"
	"sync"
	"time"
)

var DefaultContainer *Container

func Save(s *Session) error {
	return DefaultContainer.SaveSession(s)
}

func SetStorage(storage Storage) error {
	return DefaultContainer.SetStorage(storage)
}

type Container struct {
	ss             map[string]*Session
	existInStorage map[string]bool
	storage        Storage
	locker         *sync.Mutex
}

func NewContainer(storage Storage) *Container {
	if storage == nil {
		storage = NewFileStorage(Env.SessionDir)
	}
	c := &Container{
		ss: make(map[string]*Session, 10),
		//times: make(map[string]time.Time, 10),
		existInStorage: make(map[string]bool, 10),
		locker:         &sync.Mutex{},
	}
	err := c.SetStorage(storage)
	if err != nil {
		panic(err)
	}
	return c
}

func (c *Container) Len() int {
	return len(c.ss)
}

func (c *Container) Close() {
	c.locker.Lock()
	defer c.locker.Unlock()
	for id, s := range c.ss {
		for key, call := range s.beforeDestroy {
			err := call()
			if err != nil {
				log.Emergency.Printf("before destory session [%s] callback [%s] got error: %s\n", id, key, err)
			}
		}
	}
}

// 获得 Session 对象, 并更新当前 Session 的过期时间.
// 内置无锁机制的 GC 操作, 每次随机检查 n 条数据是否过期, 过期则删除之, 优点是再大的数据量都不会有卡顿,
// 缺点是对 CPU 扫描过期数据的工作量增加了 n 倍, 还会多占用 1/n 的内存(不能被立即删除)
func (c *Container) GetSession(id string) *Session {
	c.locker.Lock()
	defer c.locker.Unlock()

	checkNum := Env.CheckExpireNumEachUpdate
	now := time.Now()
	for randUUID, s := range c.ss {
		if now.After(s.update.Add(time.Duration(Env.ExpireTime))) {
			if ds, ok := c.ss[randUUID]; ok {
				if c.existInStorage[randUUID] {
					err := c.storage.Delete(randUUID)
					if err != nil {
						log.Emergency.Printf("delete session [%s] got error: %s\n", randUUID, err)
					} else {
						delete(c.existInStorage, randUUID)
					}
				}
				for key, call := range ds.beforeDestroy {
					err := call()
					if err != nil {
						log.Emergency.Printf("before destory session [%s] callback [%s] got error: %s\n", randUUID, key, err)
					}
				}
				delete(c.ss, randUUID)
			}
		}
		checkNum--
		if checkNum <= 0 {
			break
		}
	}

	session := c.ss[id]
	if session == nil {
		session = newSession(id)
		c.ss[id] = session
	}
	session.update = now
	return session
}

func (c *Container) lockCall(call func()) {
	c.locker.Lock()
	call()
	c.locker.Unlock()
}

func (c *Container) SaveSession(s *Session) error {
	if s.needSave {
		if len(s.values) == 0 {
			err := c.storage.Delete(s.id)
			if err != nil {
				return err
			}
			c.lockCall(func() {
				delete(c.existInStorage, s.id)
			})
		} else {
			data, err := MarshalSession(s)
			if err != nil {
				return err
			}
			err = c.storage.Save(s.id, data)
			if err != nil {
				return err
			}
			c.lockCall(func() {
				c.existInStorage[s.id] = true
			})
		}
		s.needSave = false
	}
	return nil
}

func (c *Container) SetStorage(s Storage) error {
	c.locker.Lock()
	defer c.locker.Unlock()
	c.storage = s
	data, err := s.ReadAll(time.Duration(Env.ExpireTime))
	if err != nil {
		return err
	}
	c.ss = make(map[string]*Session, len(data))
	now := time.Now()
	for id, sdata := range data {
		s, err := UnmarshalSession(id, sdata)
		if err != nil {
			return err
		}
		s.update = now
		c.ss[id] = s
	}
	return nil
}

type Session struct {
	id            string
	values        map[string]string      // 持久数据
	instances     map[string]interface{} // 内存数据
	needSave      bool                   // 是否需要保存至 storage
	update        time.Time
	beforeDestroy map[string]func() error
}

// call 可以返回 error, 不可以 panic, 同名的 call 将被覆盖
func (s *Session) BeforeDestroy(key string, call func() error) {
	s.beforeDestroy[key] = call
}

func (s *Session) ID() string {
	return s.id
}

func newSession(id string) *Session {
	return &Session{
		id: id,
		values: make(map[string]string, 2),
		instances: make(map[string]interface{}),
		beforeDestroy: make(map[string]func() error),
	}
}

func UnmarshalSession(id string, data []byte) (s *Session, err error) {
	s = newSession(id)
	buf := bytes.NewBuffer(data)
	err = gob.NewDecoder(buf).Decode(&s.values)
	return
}

func MarshalSession(s *Session) (data []byte, err error) {
	buf := bytes.NewBuffer(nil)
	err = gob.NewEncoder(buf).Encode(s.values)
	if err != nil {
		return nil, err
	} else {
		return buf.Bytes(), nil
	}
}

// 根据 key 获取 session 数据
func (s *Session) Get(key string) string {
	return s.values[key]
}

// 设置 session 数据
func (s *Session) Set(key, value string) {
	s.values[key] = value
	s.needSave = true
}

// 删除 session 数据
func (s *Session) Del(key string) {
	delete(s.values, key)
	s.needSave = true
}

// 根据 key 获取并删除数据
func (s *Session) Flash(key string) string {
	value := s.Get(key)
	s.Del(key)
	return value
}

// 设置内存数据, 程序退出时数据将被清除
func (s *Session) MSet(key string, value interface{}) {
	s.instances[key] = value
}

// 获得内存数据
func (s *Session) MGet(key string) (value interface{}) {
	return s.instances[key]
}

// 获得并删除内存数据
func (s *Session) MFlash(key string) (value interface{}) {
	value = s.MGet(key)
	s.MDel(key)
	return
}

// 删除内存数据
func (s *Session) MDel(key string) {
	delete(s.instances, key)
}
